package api

// web层接口

import (
	"context"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/go-redis/redis/v8"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"net/http"
	"strconv"
	"strings"
	"time"
	"web/user/forms"
	"web/user/global"
	"web/user/global/response"
	"web/user/middlewares"
	"web/user/models"
	"web/user/proto"
)

// 删除掉表单验证结果的翻译结果中的点以及点之前的内容
func removeTopStruct(field map[string]string) map[string]string {
	rsp := map[string]string{}
	for field, val := range field {
		rsp[field[strings.Index(field, ".")+1:]] = val
	}
	return rsp
}

// 将grpc的code转换成http的状态码
func HandleGrpcErrorToHttp(err error, c *gin.Context) {
	if err != nil {
		if e, ok := status.FromError(err); ok {
			switch e.Code() {
			case codes.NotFound:
				c.JSON(http.StatusNotFound, gin.H{
					"msg": e.Message(),
				})
			case codes.Internal:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg": "内部错误: " + e.Message(),
				})
			case codes.InvalidArgument:
				c.JSON(http.StatusBadRequest, gin.H{
					"msg": "参数错误: " + e.Message(),
				})
			case codes.Unavailable:
				c.JSON(http.StatusBadRequest, gin.H{
					"msg": "商品服务不可用: " + e.Message(),
				})
			default:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg": "其他错误: " + e.Message(),
				})
			}
			return
		}
	}
}

// 将表单验证的错误信息翻译为中文
func HandleValidatorError(ctx *gin.Context, err error) {
	// 将表单验证的错误信息翻译为中文
	errs, ok := err.(validator.ValidationErrors)
	if !ok {
		ctx.JSON(http.StatusBadRequest, gin.H{
			"error": err.Error(),
		})
	}

	ctx.JSON(http.StatusBadRequest, gin.H{
		// Translate()的参数是一个通用翻译器
		"error": removeTopStruct(errs.Translate(global.T)),
	})
	return
}

// 以下为专门处理请求路由的handler

// 获取用户列表
func GetUserList(ctx *gin.Context) {
	userId, _ := ctx.Get("userId")
	zap.S().Debugw("当前登录用户信息", "用户id", userId)

	pn := ctx.DefaultQuery("pn", "0")
	pnInt, _ := strconv.Atoi(pn)
	pSize := ctx.DefaultQuery("psize", "10")
	pSizeInt, _ := strconv.Atoi(pSize)

	rsp, err := global.UserSrvClient.GetUserList(context.Background(), &proto.PageInfo{
		// 优雅分页做了转换，实际上Pn=1, PSize=10
		Pn:    uint32(pnInt),
		PSize: uint32(pSizeInt),
	})
	if err != nil {
		zap.S().Error("[GetUserList] 查询 [用户列表] 失败", err)
		HandleGrpcErrorToHttp(err, ctx)
		return
	}

	result := make([]interface{}, 0)
	for _, val := range rsp.Data {
		user := response.UserResponse{
			Id:       val.Id,
			NickName: val.NickName,
			Birthday: response.JsonTime(time.Unix(int64(val.Birthday), 0)),
			Gender:   val.Gender,
			Mobile:   val.Mobile,
		}
		result = append(result, user)
	}
	ctx.JSON(http.StatusOK, result)
}

// 密码方式登录
func PassWordLogin(ctx *gin.Context) {
	// 表单验证
	passwordLoginForm := new(forms.PasswordLoginForm)
	if err := ctx.ShouldBind(passwordLoginForm); err != nil {
		HandleValidatorError(ctx, err)
		return
	}

	// 校验验证码
	// 第二个参数是用户输入的验证码内容，第三个参数是验证通过后，该验证码是否立即失效
	if !store.Verify(passwordLoginForm.CaptchaId, passwordLoginForm.Captcha, true) {
		ctx.JSON(http.StatusBadRequest, gin.H{
			"captcha": "验证码错误",
		})
		return
	}

	// 表单验证通过，调用service服务并处理登录逻辑

	// 登录的逻辑
	// 验证用户是否存在
	if rsp, err := global.UserSrvClient.GetUserByMobile(context.Background(), &proto.MobileRequest{
		Mobile: passwordLoginForm.Mobile,
	}); err != nil {
		if e, ok := status.FromError(err); ok {
			switch e.Code() {
			case codes.NotFound:
				ctx.JSON(http.StatusBadRequest, gin.H{
					"mobile": "用户不存在",
				})
			default:
				ctx.JSON(http.StatusInternalServerError, gin.H{
					"mobile": "登录失败",
				})
			}
			return
		}
	} else {
		// 用户存在
		// 调用service层检查密码的服务
		passRsp, passErr := global.UserSrvClient.CheckPassword(context.Background(), &proto.PasswordCheckInfo{
			Password:          passwordLoginForm.Password,
			EncryptedPassword: rsp.Password,
		})
		switch {
		case passErr != nil:
			ctx.JSON(http.StatusInternalServerError, gin.H{
				"msg": "登录失败",
			})
		case passRsp.Success:
			// 生成token
			j := middlewares.NewJWT()
			// token30天过期
			expiresTime := time.Now().AddDate(0, 0, 30)
			claims := models.CustomClaims{
				ID: rsp.Id,
				StandardClaims: jwt.StandardClaims{
					// 签名的生效时间
					NotBefore: time.Now().Unix(),
					ExpiresAt: expiresTime.Unix(),
					// 认证签名的机构
					Issuer: "ygShop",
				},
			}
			token, err := j.CreateToken(claims)
			if err != nil {
				ctx.JSON(http.StatusInternalServerError, gin.H{
					"msg": "生成token失败",
				})
				return
			}
			ctx.JSON(http.StatusOK, gin.H{
				"id":        rsp.Id,
				"nick_name": rsp.NickName,
				"token":     token,
				// 过期时间的毫秒级时间戳
				"expired_at": expiresTime.UnixNano() / 1e6,
			})
		case !passRsp.Success:
			ctx.JSON(http.StatusBadRequest, gin.H{
				"password": "密码错误",
			})
		}
	}
}

// 请求注册的handler
func Register(ctx *gin.Context) {
	// 表单验证
	registerForm := new(forms.RegisterForm)
	if err := ctx.ShouldBind(registerForm); err != nil {
		HandleValidatorError(ctx, err)
		return
	}

	// 校验短信验证码
	redisAddr := global.ServerConfig.RedisInfo.Host + ":" + strconv.Itoa(global.ServerConfig.RedisInfo.Port)

	rdb := redis.NewClient(&redis.Options{
		Addr: redisAddr,
	})
	key := "sms_code_" + registerForm.Mobile
	res := rdb.Get(context.Background(), key)
	if res.Err() != nil || res.Val() != registerForm.SmsCode {
		zap.S().Error("未获取验证码或验证码已过期或验证码输入错误")
		ctx.JSON(http.StatusBadRequest, gin.H{
			"msg": "验证码错误",
		})
		return
	}
	zap.S().Debug("短信验证码校验成功")

	// 创建新用户
	if rsp, err := global.UserSrvClient.CreateUser(context.Background(), &proto.CreateUserInfo{
		Mobile:   registerForm.Mobile,
		Password: registerForm.Password,
		NickName: registerForm.Mobile, // 默认以手机号作为用户昵称
	}); err != nil {
		if e, ok := status.FromError(err); ok {
			switch e.Code() {
			case codes.AlreadyExists:
				ctx.JSON(http.StatusBadRequest, gin.H{
					"mobile": "该手机号已经完成注册",
				})
			default:
				ctx.JSON(http.StatusInternalServerError, gin.H{
					"msg": "注册失败",
				})
			}
			return
		}
	} else {
		// 生成token
		j := middlewares.NewJWT()
		// token30天过期
		expiresTime := time.Now().AddDate(0, 0, 30)
		claims := models.CustomClaims{
			ID: rsp.Id,
			StandardClaims: jwt.StandardClaims{
				// 签名的生效时间
				NotBefore: time.Now().Unix(),
				ExpiresAt: expiresTime.Unix(),
				// 认证签名的机构
				Issuer: "ygShop",
			},
		}
		token, err := j.CreateToken(claims)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, gin.H{
				"msg": "生成token失败",
			})
			return
		}
		ctx.JSON(http.StatusOK, gin.H{
			"id":        rsp.Id,
			"nick_name": rsp.NickName,
			"token":     token,
			// 过期时间的毫秒级时间戳
			"expired_at": expiresTime.UnixNano() / 1e6,
		})
		return
	}
}
